﻿using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO.MemoryMappedFiles;
using System.Runtime.InteropServices;
using System.Threading;
using DirectShow;
using DirectShow.BaseClasses;
using Sonic;

namespace CamPlug.Camera
{
	[ComVisible(true)]
	[Guid("19F62C7E-2244-4015-8531-C2EA1B67DA75")]
	[AMovieSetup(Merit.Normal, "860BB310-5D01-11d0-BD3B-00A0C911CE86")]
	[PropPageSetup(typeof(FormAbout))]
	[ProgId("SigmaSevenSoftware.CamPlug.Camera")]
	public class CamPlugFilter : BaseSourceFilter, IAMFilterMiscFlags
	{
		static Mutex Mutex = new Mutex(false, CamPlug.Shared.Consts.LockingNames.BitmapResponseMutexName);

		const int DefaultWidth = 1024;
		const int DefaultHeight = 756;
		const int DefaultBitCount = 32;
		const int DefaultFPS = 20;
		const int FormatsCount = 8;
		const int WidthGranularity = 160;
		const int HeightGranularity = 120;
		const int MinimumWidth = 320;
		const int MinimumHeight = 240;
		const int MaximumWidth = MinimumWidth + WidthGranularity * (FormatsCount - 1);
		const int MaximumHeight = MinimumHeight + HeightGranularity * (FormatsCount - 1);
		const int MinimumFPS = 1;
		const int MaximumFPS = 30;

		protected int Width = DefaultWidth;
		protected int Height = DefaultHeight;
		protected int BitCount = DefaultBitCount;
		protected long AverageTimePerFrame = UNITS / DefaultFPS;
		protected BitmapInfo CurrentBitmapInfo = new BitmapInfo();

		public CamPlugFilter()
			: base("CamPlug")
		{
			CurrentBitmapInfo.bmiHeader = new BitmapInfoHeader();
			AddPin(new CamPlugStream("Capture", this));
		}

		protected override int OnInitializePins()
		{
			return NOERROR;
		}

		public int CheckMediaType(AMMediaType mediaType)
		{
			lock (m_Lock)
			{
				if (mediaType == null)
					return E_POINTER;
				if (mediaType.formatPtr == IntPtr.Zero)
					return VFW_E_INVALIDMEDIATYPE;
				if (mediaType.majorType != MediaType.Video)
					return VFW_E_INVALIDMEDIATYPE;
				if (mediaType.subType != MediaSubType.RGB24
						&& mediaType.subType != MediaSubType.RGB32
						&& mediaType.subType != MediaSubType.ARGB32
						)
				{
					return VFW_E_INVALIDMEDIATYPE;
				}

				BitmapInfoHeader bitmapInfoHeader = mediaType;
				if (bitmapInfoHeader == null)
					return E_UNEXPECTED;
				if (bitmapInfoHeader.Compression != BI_RGB)
					return VFW_E_TYPE_NOT_ACCEPTED;
				if (bitmapInfoHeader.BitCount != 24 && bitmapInfoHeader.BitCount != 32)
					return VFW_E_TYPE_NOT_ACCEPTED;

				VideoStreamConfigCaps videoStreamConfigCapabilities;
				GetDefaultCaps(0, out videoStreamConfigCapabilities);
				if (bitmapInfoHeader.Width < videoStreamConfigCapabilities.MinOutputSize.Width
						|| bitmapInfoHeader.Width > videoStreamConfigCapabilities.MaxOutputSize.Width
						)
				{
					return VFW_E_INVALIDMEDIATYPE;
				}

				long frameRate = 0;
				{
					VideoInfoHeader videoInfoHeader = mediaType;
					if (videoInfoHeader != null)
						frameRate = videoInfoHeader.AvgTimePerFrame;
				}
				{
					VideoInfoHeader2 videoInfoHeader = mediaType;
					if (videoInfoHeader != null)
						frameRate = videoInfoHeader.AvgTimePerFrame;
				}
				if (frameRate < videoStreamConfigCapabilities.MinFrameInterval
						|| frameRate > videoStreamConfigCapabilities.MaxFrameInterval
						)
				{
					return VFW_E_INVALIDMEDIATYPE;
				}

				return NOERROR;
			}
		}

		public int SetMediaType(AMMediaType mediaType)
		{
			lock (m_Lock)
			{
				BitmapInfoHeader bitmapInfoHeader = mediaType;
				CurrentBitmapInfo.bmiHeader.BitCount = bitmapInfoHeader.BitCount;
				if (bitmapInfoHeader.Height != 0)
					CurrentBitmapInfo.bmiHeader.Height = bitmapInfoHeader.Height;
				if (bitmapInfoHeader.Width > 0)
					CurrentBitmapInfo.bmiHeader.Width = bitmapInfoHeader.Width;
				CurrentBitmapInfo.bmiHeader.Compression = BI_RGB;
				CurrentBitmapInfo.bmiHeader.Planes = 1;
				CurrentBitmapInfo.bmiHeader.ImageSize =
					ALIGN16(CurrentBitmapInfo.bmiHeader.Width)
					* ALIGN16(Math.Abs(CurrentBitmapInfo.bmiHeader.Height))
					* CurrentBitmapInfo.bmiHeader.BitCount
					/ 8;
				Width = bitmapInfoHeader.Width;
				Height = bitmapInfoHeader.Height;
				BitCount = bitmapInfoHeader.BitCount;

				{
					VideoInfoHeader videoInfoHeader = mediaType;
					if (videoInfoHeader != null)
						AverageTimePerFrame = videoInfoHeader.AvgTimePerFrame;
				}
				{
					VideoInfoHeader2 videoInfoHeader = mediaType;
					if (videoInfoHeader != null)
					{
						AverageTimePerFrame = videoInfoHeader.AvgTimePerFrame;
					}
				}
			}

			return NOERROR;
		}

		public int GetMediaType(int position, ref AMMediaType mediaType)
		{
			lock (m_Lock)
			{
				if (position < 0)
					return E_INVALIDARG;

				VideoStreamConfigCaps videoStreamConfigCapabilities;
				GetDefaultCaps(0, out videoStreamConfigCapabilities);

				int width = 0;
				int height = 0;

				if (position == 0)
				{
					if (Pins.Count > 0 && Pins[0].CurrentMediaType.majorType == MediaType.Video)
					{
						mediaType.Set(Pins[0].CurrentMediaType);
						return NOERROR;
					}
					width = videoStreamConfigCapabilities.InputSize.Width;
					height = videoStreamConfigCapabilities.InputSize.Height;
				}
				else
				{
					position--;
					width = videoStreamConfigCapabilities.MinOutputSize.Width + videoStreamConfigCapabilities.OutputGranularityX * position;
					height = videoStreamConfigCapabilities.MinOutputSize.Height + videoStreamConfigCapabilities.OutputGranularityY * position;
					if (width > videoStreamConfigCapabilities.MaxOutputSize.Width || height > videoStreamConfigCapabilities.MaxOutputSize.Height)
						return VFW_S_NO_MORE_ITEMS;
				}

				mediaType.majorType = MediaType.Video;
				mediaType.formatType = FormatType.VideoInfo;

				VideoInfoHeader videoInfoHeader = new VideoInfoHeader();
				videoInfoHeader.AvgTimePerFrame = AverageTimePerFrame;
				videoInfoHeader.BmiHeader.Compression = BI_RGB;
				videoInfoHeader.BmiHeader.BitCount = (short)BitCount;
				videoInfoHeader.BmiHeader.Width = width;
				videoInfoHeader.BmiHeader.Height = height;
				videoInfoHeader.BmiHeader.Planes = 1;
				videoInfoHeader.BmiHeader.ImageSize =
					videoInfoHeader.BmiHeader.Width
					* Math.Abs(videoInfoHeader.BmiHeader.Height)
					* videoInfoHeader.BmiHeader.BitCount
					/ 8;

				switch (videoInfoHeader.BmiHeader.BitCount)
				{
					case 32:
						mediaType.subType = MediaSubType.RGB32;
						break;

					case 24:
						mediaType.subType = MediaSubType.RGB24;
						break;
				}
				AMMediaType.SetFormat(ref mediaType, ref videoInfoHeader);
				mediaType.fixedSizeSamples = true;
				mediaType.sampleSize = videoInfoHeader.BmiHeader.ImageSize;

				return NOERROR;
			}
		}

		public int DecideBufferSize(ref IMemAllocatorImpl memoryAllocator, ref AllocatorProperties allocatorProperties)
		{
			lock (m_Lock)
			{
				AllocatorProperties newAllocatorProperties = new AllocatorProperties();

				BitmapInfoHeader bitmapInfoHeader = (BitmapInfoHeader)Pins[0].CurrentMediaType;
				allocatorProperties.cbBuffer = bitmapInfoHeader.GetBitmapSize();

				if (allocatorProperties.cbBuffer < bitmapInfoHeader.ImageSize)
					allocatorProperties.cbBuffer = bitmapInfoHeader.ImageSize;
				if (allocatorProperties.cbBuffer < CurrentBitmapInfo.bmiHeader.ImageSize)
					allocatorProperties.cbBuffer = CurrentBitmapInfo.bmiHeader.ImageSize;

				allocatorProperties.cbBuffer *= 2;
				allocatorProperties.cBuffers = 1;
				allocatorProperties.cbAlign = 1;
				allocatorProperties.cbPrefix = 0;
				int result = memoryAllocator.SetProperties(allocatorProperties, newAllocatorProperties);
				return result;
			}
		}

		public int FillBuffer(ref IMediaSampleImpl sample)
		{
			lock (m_Lock)
			{
				IntPtr samplePointer;
				sample.GetPointer(out samplePointer);

				Bitmap bitmap;
				if (!ObtainBitmap(out bitmap))
					bitmap = Properties.Resources.Testcard;

				IntPtr sampleBufferPointer;
				sample.GetPointer(out sampleBufferPointer);


				var hScreenDC = CreateDC("DISPLAY", null, null, IntPtr.Zero);
				var hMemDC = CreateCompatibleDC(hScreenDC);
				var hMemBitmap = CreateCompatibleBitmap(hScreenDC, Width, Height);
				var hOldBitmap = SelectObject(hMemDC, hMemBitmap);

				var canvas = Graphics.FromHdc(hMemDC);
				canvas.DrawImage(bitmap, new Rectangle(0, 0, Width, Height));
				GetDIBits(hMemDC, hMemBitmap, 0, (uint)Math.Abs(Height), sampleBufferPointer, ref CurrentBitmapInfo, 0);

				GC.KeepAlive(canvas);
				GC.KeepAlive(bitmap);

				SelectObject(hMemDC, hOldBitmap);
				DeleteObject(hMemBitmap);
				DeleteObject(hMemDC);
				DeleteObject(hScreenDC);
				sample.SetActualDataLength(sample.GetSize());
				sample.SetSyncPoint(true);
				return NOERROR;
			}
		}

		bool ObtainBitmap(out Bitmap bitmap)
		{
			try
			{
				Mutex.WaitOne();
				using (var file = MemoryMappedFile.OpenExisting(
						mapName: CamPlug.Shared.Consts.MemoryMapNames.BitmapResponseMappingName)
					)
				{
					using (var viewStream = file.CreateViewStream())
						bitmap = new Bitmap(viewStream);
					return true;
				}
			}
			catch
			{
				bitmap = null;
				return false;
			}
			finally
			{
				Mutex.ReleaseMutex();
			}
		}

		public int GetLatency(out long latency)
		{
			lock (m_Lock)
			{
				latency = UNITS / 30;
				AMMediaType mediaType = Pins[0].CurrentMediaType;
				if (mediaType.majorType == MediaType.Video)
				{
					{
						VideoInfoHeader videoInfoHeader = mediaType;
						if (videoInfoHeader != null)
							latency = videoInfoHeader.AvgTimePerFrame;
					}
					{
						VideoInfoHeader2 videoInfoHeader = mediaType;
						if (videoInfoHeader != null)
						{
							latency = videoInfoHeader.AvgTimePerFrame;
						}
					}
				}
				return NOERROR;
			}
		}

		public int GetNumberOfCapabilities(out int count, out int size)
		{
			lock (m_Lock)
			{
				count = 0;
				AMMediaType mediaType = new AMMediaType();
				while (GetMediaType(count, ref mediaType) == S_OK)
				{
					mediaType.Free();
					count++;
				};
				size = Marshal.SizeOf(typeof(VideoStreamConfigCaps));
				return NOERROR;
			}
		}

		public int GetStreamCaps(int index, out AMMediaType mediaType, out VideoStreamConfigCaps videoStreamConfigCapabilities)
		{
			lock (m_Lock)
			{
				mediaType = null;
				videoStreamConfigCapabilities = null;
				if (index < 0)
					return E_INVALIDARG;

				mediaType = new AMMediaType();
				HRESULT result = (HRESULT)GetMediaType(index, ref mediaType);
				if (FAILED(result))
					return result;
				if (result == VFW_S_NO_MORE_ITEMS)
					return S_FALSE;
				result = (HRESULT)GetDefaultCaps(index, out videoStreamConfigCapabilities);
				return result;
			}
		}

		public int SuggestAllocatorProperties(AllocatorProperties allocatorProperties)
		{
			lock (m_Lock)
			{
				AllocatorProperties newAllocatorProperties = new AllocatorProperties();
				HRESULT result = (HRESULT)GetAllocatorProperties(newAllocatorProperties);
				if (FAILED(result))
					return result;
				if (allocatorProperties.cbBuffer != -1
						&& allocatorProperties.cbBuffer < newAllocatorProperties.cbBuffer
						)
				{
					return E_FAIL;
				}
				if (allocatorProperties.cbAlign != -1 && allocatorProperties.cbAlign != newAllocatorProperties.cbAlign)
					return E_FAIL;
				if (allocatorProperties.cbPrefix != -1 && allocatorProperties.cbPrefix != newAllocatorProperties.cbPrefix)
					return E_FAIL;
				if (allocatorProperties.cBuffers != -1 && allocatorProperties.cBuffers < 1)
					return E_FAIL;

				return NOERROR;
			}
		}

		public int GetAllocatorProperties(AllocatorProperties allocatorProperties)
		{
			lock (m_Lock)
			{
				AMMediaType mediaType = Pins[0].CurrentMediaType;
				if (mediaType.majorType == MediaType.Video)
				{
					int size = mediaType.sampleSize;
					BitmapInfoHeader bitmapInfoHeader = mediaType;
					if (bitmapInfoHeader != null)
					{
						if (size < bitmapInfoHeader.GetBitmapSize())
							size = bitmapInfoHeader.GetBitmapSize();
						if (size < bitmapInfoHeader.ImageSize)
							size = bitmapInfoHeader.ImageSize;
					}
					allocatorProperties.cbBuffer = size;
					allocatorProperties.cBuffers = 1;
					allocatorProperties.cbAlign = 1;
					allocatorProperties.cbPrefix = 0;
				}
				return NOERROR;
			}
		}

		public int GetDefaultCaps(int index, out VideoStreamConfigCaps videoStreamConfigCapabilities)
		{
			lock (m_Lock)
			{
				videoStreamConfigCapabilities = new VideoStreamConfigCaps();
				videoStreamConfigCapabilities.guid = FormatType.VideoInfo;
				videoStreamConfigCapabilities.VideoStandard = AnalogVideoStandard.None;
				videoStreamConfigCapabilities.InputSize.Width = DefaultWidth;
				videoStreamConfigCapabilities.InputSize.Height = DefaultHeight;
				videoStreamConfigCapabilities.MinCroppingSize.Width = MinimumWidth;
				videoStreamConfigCapabilities.MinCroppingSize.Height = MinimumHeight;
				videoStreamConfigCapabilities.MaxCroppingSize.Width = MaximumWidth;
				videoStreamConfigCapabilities.MaxCroppingSize.Height = MaximumHeight;
				videoStreamConfigCapabilities.CropGranularityX = WidthGranularity;
				videoStreamConfigCapabilities.CropGranularityY = HeightGranularity;
				videoStreamConfigCapabilities.CropAlignX = 0;
				videoStreamConfigCapabilities.CropAlignY = 0;
				videoStreamConfigCapabilities.MinOutputSize.Width = videoStreamConfigCapabilities.MinCroppingSize.Width;
				videoStreamConfigCapabilities.MinOutputSize.Height = videoStreamConfigCapabilities.MinCroppingSize.Height;
				videoStreamConfigCapabilities.MaxOutputSize.Width = videoStreamConfigCapabilities.MaxCroppingSize.Width;
				videoStreamConfigCapabilities.MaxOutputSize.Height = videoStreamConfigCapabilities.MaxCroppingSize.Height;
				videoStreamConfigCapabilities.OutputGranularityX = videoStreamConfigCapabilities.CropGranularityX;
				videoStreamConfigCapabilities.OutputGranularityY = videoStreamConfigCapabilities.CropGranularityY;
				videoStreamConfigCapabilities.StretchTapsX = 0;
				videoStreamConfigCapabilities.StretchTapsY = 0;
				videoStreamConfigCapabilities.ShrinkTapsX = 0;
				videoStreamConfigCapabilities.ShrinkTapsY = 0;
				videoStreamConfigCapabilities.MinFrameInterval = UNITS / MaximumFPS;
				videoStreamConfigCapabilities.MaxFrameInterval = UNITS / MinimumFPS;
				videoStreamConfigCapabilities.MinBitsPerSecond =
					videoStreamConfigCapabilities.MinOutputSize.Width
					* videoStreamConfigCapabilities.MinOutputSize.Height
					* DefaultBitCount
					* MinimumFPS;
				videoStreamConfigCapabilities.MaxBitsPerSecond =
					videoStreamConfigCapabilities.MaxOutputSize.Width
					* videoStreamConfigCapabilities.MaxOutputSize.Height
					* DefaultBitCount
					* MaximumFPS;

				return NOERROR;
			}
		}

		#region IAMFilterMiscFlags Members

		public int GetMiscFlags()
		{
			return (int)AMFilterMiscFlags.IsSource;
		}

		#endregion

		#region API

		[StructLayout(LayoutKind.Sequential)]
		protected struct BitmapInfo
		{
			public BitmapInfoHeader bmiHeader;
			public int[] bmiColors;
		}

		enum TernaryRasterOperations : uint
		{
			SRCCOPY = 0x00CC0020,
			SRCPAINT = 0x00EE0086,
			SRCAND = 0x008800C6,
			SRCINVERT = 0x00660046,
			SRCERASE = 0x00440328,
			NOTSRCCOPY = 0x00330008,
			NOTSRCERASE = 0x001100A6,
			MERGECOPY = 0x00C000CA,
			MERGEPAINT = 0x00BB0226,
			PATCOPY = 0x00F00021,
			PATPAINT = 0x00FB0A09,
			PATINVERT = 0x005A0049,
			DSTINVERT = 0x00550009,
			BLACKNESS = 0x00000042,
			WHITENESS = 0x00FF0062,
			CAPTUREBLT = 0x40000000
		}

		[DllImport("gdi32.dll")]
		static extern IntPtr CreateDC(string lpszDriver, string lpszDevice, string lpszOutput, IntPtr lpInitData);

		[DllImport("gdi32.dll", SetLastError = true)]
		static extern IntPtr CreateCompatibleDC(IntPtr hdc);

		[DllImport("gdi32.dll")]
		static extern IntPtr CreateCompatibleBitmap(IntPtr hdc, int nWidth, int nHeight);

		[DllImport("gdi32.dll", ExactSpelling = true, PreserveSig = true, SetLastError = true)]
		static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj);

		[DllImport("gdi32.dll")]
		static extern bool DeleteObject(IntPtr hObject);

		[DllImport("gdi32.dll")]
		static extern bool DeleteDC(IntPtr hdc);

		[DllImport("gdi32.dll")]
		static extern bool StretchBlt(IntPtr hdcDest, int nXOriginDest, int nYOriginDest,
				int nWidthDest, int nHeightDest,
				IntPtr hdcSrc, int nXOriginSrc, int nYOriginSrc, int nWidthSrc, int nHeightSrc,
				TernaryRasterOperations dwRop);

		[DllImport("gdi32.dll")]
		static extern int GetDIBits(IntPtr hdc, IntPtr hbmp, uint uStartScan,
			 uint cScanLines, [Out] IntPtr lpvBits, ref BitmapInfo lpbmi, uint uUsage);

		[DllImport("gdi32.dll")]
		static extern int GetDeviceCaps(IntPtr hdc, int nIndex);

		#endregion
	}

}
